Skip to main content

aviutl2\generic\binding/
project.rs

1use crate::load_wide_string;
2
3/// プロジェクトファイルにデータを保存・取得するための構造体。
4pub struct ProjectFile<'a> {
5    pub(crate) internal: *mut aviutl2_sys::plugin2::PROJECT_FILE,
6    _marker: std::marker::PhantomData<&'a ()>,
7}
8
9/// プロジェクトファイルのデータ取得・保存に関するエラー。
10#[derive(thiserror::Error, Debug)]
11pub enum ProjectFileError {
12    #[error("key contains null byte: {0}")]
13    KeyContainsNull(std::ffi::NulError),
14    #[error("data retrieval failed for key {0}")]
15    RetrievalFailed(String),
16    #[error("data length exceeds 4096 bytes, got {0} bytes")]
17    DataTooLarge(usize),
18    #[error("value contains null byte: {0}")]
19    ValueContainsNull(std::ffi::NulError),
20}
21
22impl<'a> ProjectFile<'a> {
23    /// 生ポインタから`ProjectFile`を作成します。
24    ///
25    /// # Safety
26    ///
27    /// - `raw`は有効な`PROJECT_FILE`ポインタである必要があります。
28    pub unsafe fn from_raw(raw: *mut aviutl2_sys::plugin2::PROJECT_FILE) -> Self {
29        Self {
30            internal: raw,
31            _marker: std::marker::PhantomData,
32        }
33    }
34
35    /// プロジェクトに保存されている文字列を取得します。
36    ///
37    /// # Errors
38    ///
39    /// - `key`にヌル文字が含まれている場合、失敗します。
40    /// - 文字列が見つからなかった場合は失敗します。
41    pub fn get_param_string(&self, key: &str) -> Result<String, ProjectFileError> {
42        let c_key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
43        unsafe {
44            let raw_str = ((*self.internal).get_param_string)(c_key.as_ptr() as _);
45            if raw_str.is_null() {
46                return Err(ProjectFileError::RetrievalFailed(key.to_string()));
47            }
48            Ok(std::ffi::CStr::from_ptr(raw_str)
49                .to_string_lossy()
50                .into_owned())
51        }
52    }
53
54    /// プロジェクトに保存されているバイナリデータを取得します。
55    ///
56    /// # Errors
57    ///
58    /// - `key`にヌル文字が含まれている場合、失敗します。
59    /// - `data` の長さが保存されているデータの長さと一致しない場合、失敗します。
60    /// - 指定されたキーに対応するデータが存在しない場合、失敗します。
61    pub fn get_param_binary(&self, key: &str, data: &mut [u8]) -> Result<(), ProjectFileError> {
62        let success = unsafe {
63            let key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
64            ((*self.internal).get_param_binary)(
65                key.as_ptr() as _,
66                data.as_mut_ptr() as _,
67                data.len() as _,
68            )
69        };
70        if !success {
71            return Err(ProjectFileError::RetrievalFailed(key.to_string()));
72        }
73        Ok(())
74    }
75
76    /// プロジェクトに文字列を保存します。
77    ///
78    /// # Errors
79    ///
80    /// key、valueにヌル文字が含まれている場合、失敗します。
81    pub fn set_param_string(&mut self, key: &str, value: &str) -> Result<(), ProjectFileError> {
82        let key_cstr = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
83        let value_cstr =
84            std::ffi::CString::new(value).map_err(ProjectFileError::ValueContainsNull)?;
85        unsafe {
86            ((*self.internal).set_param_string)(key_cstr.as_ptr() as _, value_cstr.as_ptr() as _);
87        }
88        Ok(())
89    }
90
91    /// プロジェクトにバイナリデータを保存します。
92    ///
93    /// # Errors
94    ///
95    /// - `data` の長さが4096バイトを超える場合、失敗します。
96    /// - `key`にヌル文字が含まれている場合、失敗します。
97    pub fn set_param_binary(&mut self, key: &str, data: &[u8]) -> Result<(), ProjectFileError> {
98        if data.len() > 4096 {
99            return Err(ProjectFileError::DataTooLarge(data.len()));
100        }
101        unsafe {
102            let key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
103            ((*self.internal).set_param_binary)(
104                key.as_ptr() as _,
105                data.as_ptr() as _,
106                data.len() as _,
107            );
108        }
109        Ok(())
110    }
111
112    /// プロジェクトに保存されているデータをすべて削除します。
113    pub fn clear_params(&mut self) {
114        unsafe { ((*self.internal).clear_params)() }
115    }
116
117    /// プロジェクトファイルのパスを取得します。
118    pub fn get_path(&self) -> Option<std::path::PathBuf> {
119        unsafe {
120            let raw_str = ((*self.internal).get_project_file_path)();
121            if raw_str.is_null() {
122                return None;
123            }
124            Some(std::path::PathBuf::from(load_wide_string(raw_str)))
125        }
126    }
127}
128
129#[cfg(feature = "serde")]
130const _: () = {
131    use std::io::Read;
132
133    static NAMESPACE: &str = "--aviutl2-rs";
134
135    /// プロジェクトのシリアライズ・デシリアライズ関連のエラー。
136    #[derive(thiserror::Error, Debug)]
137    pub enum ProjectFileSerdeError {
138        #[error("serialization error: {0}")]
139        Serialization(#[from] rmp_serde::encode::Error),
140        #[error("deserialization error: {0}")]
141        Deserialization(#[from] rmp_serde::decode::Error),
142        #[error("zstd dompression error: {0}")]
143        Decompression(#[from] std::io::Error),
144        #[error("project file error: {0}")]
145        ProjectFile(#[from] ProjectFileError),
146        #[error("unsupported serialization format")]
147        UnsupportedFormat,
148        #[error("invalid header format: {0}")]
149        InvalidHeaderFormat(String),
150        #[error("incomplete data retrieved for key")]
151        IncompleteData,
152    }
153
154    impl<'a> ProjectFile<'a> {
155        /// プロジェクトにデータをシリアライズして保存します。
156        ///
157        /// # Note
158        ///
159        /// 今現在の実装ではデータはMessagePackにシリアライズされています。
160        ///
161        /// # Errors
162        ///
163        /// - シリアライズに失敗した場合。
164        pub fn serialize<T: serde::Serialize>(
165            &mut self,
166            key: &str,
167            value: &T,
168        ) -> Result<(), ProjectFileSerdeError> {
169            let bytes = rmp_serde::to_vec_named(value)?;
170            let num_bytes = bytes.len();
171            self.set_param_string(key, &format!("{NAMESPACE}:serde-rmp-v1:{}", num_bytes))?;
172            for (i, chunk) in bytes.chunks(4096).enumerate() {
173                let chunk_key = format!("{NAMESPACE}:serde-chunk:{}:{}", key, i);
174                self.set_param_binary(&chunk_key, chunk)?;
175            }
176            Ok(())
177        }
178
179        /// プロジェクトからデータをデシリアライズして取得します。
180        pub fn deserialize<T: serde::de::DeserializeOwned>(
181            &self,
182            key: &str,
183        ) -> Result<T, ProjectFileSerdeError> {
184            let header = self.get_param_string(key)?;
185            if let Ok(value) = self.decode_serde_zstd_v1(key, &header) {
186                return Ok(value);
187            }
188            self.decode_serde_rmp_v1(key, &header)
189        }
190
191        fn decode_serde_rmp_v1<T: serde::de::DeserializeOwned>(
192            &self,
193            key: &str,
194            header: &str,
195        ) -> Result<T, ProjectFileSerdeError> {
196            let header_prefix = format!("{NAMESPACE}:serde-rmp-v1:");
197            let num_bytes = header
198                .strip_prefix(&header_prefix)
199                .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
200            let num_bytes: usize = num_bytes
201                .parse()
202                .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
203            if num_bytes == 0 {
204                return Err(ProjectFileSerdeError::InvalidHeaderFormat(
205                    header.to_string(),
206                ));
207            }
208            let chunks = self.collect_chunks(num_bytes, key)?;
209            let value: T = rmp_serde::from_slice(&chunks)?;
210            Ok(value)
211        }
212
213        fn collect_chunks(
214            &self,
215            num_bytes: usize,
216            key: &str,
217        ) -> Result<Vec<u8>, ProjectFileSerdeError> {
218            let mut bytes = Vec::with_capacity(num_bytes);
219            let mut read_bytes = 0;
220            let mut chunk = vec![0u8; 4096];
221            for i in 0.. {
222                let chunk_key = format!("{NAMESPACE}:serde-chunk:{}:{}", key, i);
223                let to_read = std::cmp::min(4096, num_bytes - read_bytes);
224                chunk.resize(to_read, 0);
225                match self.get_param_binary(&chunk_key, &mut chunk) {
226                    Ok(()) => {
227                        bytes.extend_from_slice(&chunk);
228                        read_bytes += to_read;
229                        if read_bytes >= num_bytes {
230                            break;
231                        }
232                    }
233                    Err(_) => break,
234                }
235            }
236            if read_bytes != num_bytes {
237                return Err(ProjectFileSerdeError::IncompleteData);
238            }
239            Ok(bytes)
240        }
241        fn decode_serde_zstd_v1<T: serde::de::DeserializeOwned>(
242            &self,
243            key: &str,
244            header: &str,
245        ) -> Result<T, ProjectFileSerdeError> {
246            let header_prefix = format!("{NAMESPACE}:serde-zstd-v1:");
247            let num_bytes = header
248                .strip_prefix(&header_prefix)
249                .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
250            let num_bytes: usize = num_bytes
251                .parse()
252                .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
253            if num_bytes == 0 {
254                return Err(ProjectFileSerdeError::InvalidHeaderFormat(
255                    header.to_string(),
256                ));
257            }
258            let mut bytes = Vec::with_capacity(num_bytes);
259            let mut read_bytes = 0;
260            let mut chunk = vec![0u8; 4096];
261            for i in 0.. {
262                let chunk_key = format!("{NAMESPACE}:serde-zstd-v1:chunk:{}:{}", key, i);
263                let to_read = std::cmp::min(4096, num_bytes - read_bytes);
264                chunk.resize(to_read, 0);
265                match self.get_param_binary(&chunk_key, &mut chunk) {
266                    Ok(()) => {
267                        bytes.extend_from_slice(&chunk);
268                        read_bytes += to_read;
269                        if read_bytes >= num_bytes {
270                            break;
271                        }
272                    }
273                    Err(_) => break,
274                }
275            }
276            if read_bytes != num_bytes {
277                return Err(ProjectFileSerdeError::IncompleteData);
278            }
279            let mut decoder = ruzstd::decoding::StreamingDecoder::new(&bytes[..])
280                .map_err(|e| ProjectFileSerdeError::Decompression(std::io::Error::other(e)))?;
281            let mut decompressed_bytes = vec![];
282            decoder.read_to_end(&mut decompressed_bytes)?;
283            let value: T = rmp_serde::from_slice(&decompressed_bytes)?;
284            Ok(value)
285        }
286    }
287};